module necrophilia.helpers.Interpolation;
import maths.Vec;
import maths.Quat;
import utils.Memory;

import necrophilia.helpers.Callback;

/** 
	Another way of making necrophilia flexible is that the programmer is easily able to choose
	interpolation method wherever interpolation is done. To make it easy and readable 
	interpolation is holded in a machine represented by template structure Interpolations.
	The interpolation can be either a delegate or a function. Let's give an example. We would
	like to do nearest interpolation. 
	Examples:
	-----------
	
	-----------
*/
struct Interpolation(Time){
	
	/**
		Callback holding vector interpolations
	*/
	alias InterpolationCallback!(vec3, Time, vec3) VecInterpolation;
	
	/**
		Callback holding rot interpolations
	*/
	alias InterpolationCallback!(quat, Time, quat) RotInterpolation;
	
	
	private VecInterpolation vecInterpolation;
	private RotInterpolation quatInterpolation;
	int offsets[]; /// interpolation offsets
	
	void set(vec3 delegate (Time time, vec3[] vecs ...) del){
		vecInterpolation.set(del);
	}
	
	void set(vec3 function (Time time, vec3[] vecs ...) func){
		vecInterpolation.set(func);
	}
	
	void set(quat delegate (Time time, quat[] quats ...) del){
		quatInterpolation.set(del);
	}
	
	void set(quat function (Time time, quat[] quats ...) func){
		quatInterpolation.set(func);
	}
	
	quat interpolate(Time time, quat[] quats ...){
		return quatInterpolation.call(time,quats);
	}
	
	vec3 interpolate(Time time, vec3[] vecs ...){
		return vecInterpolation.call(time,vecs);
	}
		
}

/**
	Interface for objects with adjastable interpolation method. To set the interpolation method just give a delagete or pointer to a function
	and offsets as the second parameter. What offsets are?
	as we heve our keyframes:
	Examples:
	--------------
	[[keyframe 1], [keyframe 2], [keyframe 3] , ... , [keyframe 4]]
	--------------
	one is always the current one, let say
	Examples:
	--------------
	[[keyframe 1], [keyframe 2], [keyframe 3] , ... , [keyframe n]]
								/|\
								 |  current
	--------------
	current frame has offset == 0. so in this case, keyframe 1 has offset==-1 . Whats more keyframe n in this case has offset -2. 
	What gives us great oportunity. Even if we have 2 keyframe animation we can use interpolations fo 4 keyframes and more
	what will smooth bone transformation. So reasuming. When we call .setInterpolationMethod( &interpolatiomMeth, -1, 0, 1); will mean
	use interpolation method from function interpolationMeth, and as args always give the previous, current and next frame.
*/
interface dynamiclyInterpolated(Time){
	void setInterpolationMethod(vec3 delegate (Time time, vec3[] vecs...) del); /// setting a delegate as a vec interpolation method
	void setInterpolationMethod(quat delegate (Time time, quat[] vecs...) del); /// setting a delegate as a quat interpolation method
	void setInterpolationMethod(vec3 function (Time time, vec3[] vecs...) func); ///  setting a function as a vec interpolation method 
	void setInterpolationMethod(quat function (Time time, quat[] vecs...) func); ///  setting a function as a quat interpolation method 
	void setOffsets(int[] offsets ...); /// setting offsets
}

/* 
	+++++++++++++++++++++++++++++++ Unit test stuff ++++++++++++++++++++++++++++++++++++++++++++++++
*/


version ( Unittest) {
	import std.stdio;
	vec3 check(float time,vec3[] v...){
		assert (v[0].x==2.0f);
		assert (v[1].x==4.0f);
		assert (time==0.5f);
		return vec3.zero;
	}
	
	vec3 check2(float time,vec3[] v...){
		assert (v[0].x==9.0f);
		assert (v[1].x==1.0f);
		assert (time==0.5f);
		return vec3.zero;
	}
	
	class Foo(Time):dynamiclyInterpolated!(Time){
		vec3[] vecs;
		vec3[] param;
		Interpolation!(Time) interpolationMeth;
		float time;
		int current;
		
		this(){
			vecs=new vec3[10];
			foreach(index, inout vec;vecs){
				vec=vec3.zero;
				vec.x=index;
			}
			time=0.5;
			current=3;
		}
		
		void setInterpolationMethod(vec3 delegate (Time time, vec3[] vecs...) del){
			interpolationMeth.set(del);
		}
		
		void setInterpolationMethod(vec3 function (Time time, vec3[] vecs...) func){
			interpolationMeth.set(func);
		}
		
		void setInterpolationMethod(quat delegate (Time time, quat[] vecs...) del){}
		
		void setInterpolationMethod(quat function (Time time, quat[] vecs...) func){}
		
		void setOffsets(int[] offsets ...){
			interpolationMeth.offsets=offsets.clone();
			param=new vec3[offsets.length];
		}
		
		void work(){
			foreach(index,offset;interpolationMeth.offsets){
				
				int tmp=current+offset;
				while(tmp<0) tmp+=vecs.length;
				param[index]=vecs[tmp%vecs.length];
			}
			assert(interpolationMeth.interpolate(time,param) == vec3.zero);
		}
	}}

unittest {
	version ( Unittest) {
		Foo!(float) foo=new Foo!(float)();
		foo.setOffsets(-1,1);
		foo.setInterpolationMethod(&check);
		foo.work;
		foo.current=0;
		foo.setInterpolationMethod(&check2);
		foo.work;
	}
}

